package org.evolAI.organism.domain;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MapKey;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;

import org.evolAI.organism.util.RandomNum;

@Entity
@Table(name="LongTermMemory")
@XmlRootElement
public class LongTermMemory implements Map<String, MemoryGroup> {
	
	private long id;
	private Map<String, MemoryGroup> memories = new HashMap<String, MemoryGroup>();

	public LongTermMemory() {
		
	}
	
	protected LongTermMemory(LongTermMemory memory) {
		for (Entry<String, MemoryGroup> entry: memory.entrySet()) {
			this.put(entry.getKey(), entry.getValue().copy());
		}
	}
	
    public LongTermMemory copy() {
        return new LongTermMemory(this);
    }

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "memories_id")
	public long getId() {
		return this.id;
	}
	
	public void setId(long id) {
		this.id = id;
	}

	@OneToMany(cascade=CascadeType.ALL)
	@MapKey
	public Map<String, MemoryGroup> getMemories() {
		return memories;
	}
	
	public void setMemories(Map<String, MemoryGroup> memories) {
		this.memories = memories;
	}

	@Transient
	public int getMemoryCount() {
        int count = 0;
        for (MemoryGroup group: this.values()) {
            count += group.size();
        }
        return count;
    }

    public void remember(String key, String memory) {
        MemoryGroup memories;
        if (memory != null && key != null) {
            if (key.length() > 100)
                key = key.substring(0, 100);
            if (containsKey(key)) {
                this.get(key).remember(memory);
            }
            else {
                memories = new MemoryGroup();
                if (memory.length() > 100)
                    memory = memory.substring(0, 100);
                memories.addMemory(memory);
                put(key, memories);
            }
        }
    }

    public MemoryGroup recall(String key) {
        return this.get(key);
    }

    public String recallRandom() {
        String[] keys = this.keySet().toArray(new String[0]);

        if (keys.length > 0)
        {
            String key = keys[RandomNum.getUniform(keys.length)];
            return recallRandom(key);
        }
        return "";
    }

    public String recallRandom(String key) {
        if (key != null && containsKey(key)) {
            return this.get(key).recallRandom();
        }
        return "";
    }

    public String recallEarliest(String key) {
        if (key != null && containsKey(key)) {
            return this.get(key).recallEarliest();
        }
        return "";
    }

    public String recallLatest(String key) {
        if (key != null && containsKey(key)) {
            return this.get(key).recallLatest();
        }
        return "";
    }

    public String recallStrongest(String key) {
        if (key != null && containsKey(key)) {
            return this.get(key).recallStrongest();
        }
        return "";
    }

    public void degrade() {
        for (MemoryGroup aMemory: this.values()) {
            aMemory.degrade();
        }
    }

    public String dump() {
        StringBuilder sbDump = new StringBuilder();
        for (Entry<String, MemoryGroup> entry: this.entrySet()) {
            sbDump.append(entry.getKey());
            sbDump.append("::");
            for (Entry<String, Memory> sMem: entry.getValue().entrySet()) {
                sbDump.append("<");
                sbDump.append(sMem.getValue().getWeight());
                sbDump.append(":");
                sbDump.append(sMem.getKey());
                sbDump.append(">");
                if (sbDump.length() > 4096)
                    break;
            }
            sbDump.append("\n");
        }
        return sbDump.toString();
    }

    public boolean contains(String search) {
        for (MemoryGroup aMem: this.values()) {
            for (String sMem: aMem.keySet()) {
                if (sMem.equals(search)) {
                    return true;
                }
            }
        }
        return false;
    }

	@Override
	public int size() {
		return memories.size();
	}

	@Override
	@Transient
	public boolean isEmpty() {
		return memories.isEmpty();
	}

	@Override
	public boolean containsKey(Object key) {
		return memories.containsKey(key);
	}

	@Override
	public boolean containsValue(Object value) {
		return memories.containsValue(value);
	}

	@Override
	public MemoryGroup get(Object key) {
		return memories.get(key);
	}

	@Override
	public MemoryGroup put(String key, MemoryGroup value) {
		return memories.put(key, value);
	}

	@Override
	public MemoryGroup remove(Object key) {
		return memories.remove(key);
	}

	@Override
	public void putAll(Map<? extends String, ? extends MemoryGroup> m) {
		memories.putAll(m);
	}

	@Override
	public void clear() {
		memories.clear();
	}

	@Override
	public Set<String> keySet() {
		return memories.keySet();
	}

	@Override
	public Collection<MemoryGroup> values() {
		return memories.values();
	}

	@Override
	public Set<java.util.Map.Entry<String, MemoryGroup>> entrySet() {
		return memories.entrySet();
	}

}
